package com.ccx.demo.business.user.service;

import com.alibaba.fastjson.JSON;
import com.ccx.demo.business.common.vo.UserFileInfo;
import com.ccx.demo.business.user.cache.ITabUserCache;
import com.ccx.demo.business.user.dao.jpa.UserRepository;
import com.ccx.demo.business.user.dto.TabUserInsertDTO;
import com.ccx.demo.business.user.dto.TabUserUpdateDTO;
import com.ccx.demo.business.user.entity.QTabUser;
import com.ccx.demo.business.user.entity.TabRole;
import com.ccx.demo.business.user.entity.TabUser;
import com.ccx.demo.business.user.vo.TabUserVO;
import com.ccx.demo.config.init.AppConfig;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.querydsl.core.QueryResults;
import com.support.mvc.entity.base.Page;
import com.support.mvc.enums.AppCode;
import com.support.mvc.exception.DeleteRowsException;
import com.support.mvc.exception.UpdateRowsException;
import com.utils.enums.Code;
import com.utils.util.Rsa;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.redisson.api.RBatch;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.ccx.demo.config.init.AppInit.getCacheManager;

/**
 * 服务接口实现类：用户
 *
 * @author 谢长春 on 2017/10/12.
 */
@Slf4j
@Service

@Validated
@RequiredArgsConstructor
public class UserService implements ITabUserCache {

    @Lazy
    @Autowired
    private RoleService roleService;

    private final AppConfig appConfig;
    private final UserRepository repository;
    private final PasswordEncoder passwordEncoder;
    private final RedissonClient redissonClient;
//    private UserCache userCache;
//    private PhoneCode phoneCode;

    /**
     * 获取当前缓存管理器，用于代码控制缓存
     *
     * @return {@link Cache}
     */
    public Cache getCache() {
        return Objects.requireNonNull(getCacheManager().getCache(CACHE_ROW_BY_ID), "未获取到缓存管理对象:".concat(CACHE_ROW_BY_ID));
    }

    /**
     * 清除多个 key 对应的缓存
     *
     * @param ids {@link TabRole#getId()}
     */
    public void clearKeys(final Collection<Long> ids) {
        final Cache cache = getCache();
        ids.stream().distinct().forEach(cache::evict);
    }

    /**
     * 清除指定用户的缓存
     *
     * @param id Long 用户ID
     */
    private void clearCache(final Long id) {
        clearKeys(Sets.newHashSet(id));
        final Cache cache = Objects.requireNonNull(getCacheManager().getCache(CACHE_LOGIN), "未获取到缓存管理对象:".concat(CACHE_LOGIN));
        repository.findById(id).ifPresent(obj -> { // 清除登录查询缓存
            cache.evict(obj.getUsername());
            cache.evict(obj.getPhone());
            cache.evict(obj.getEmail());
        });
    }

    /**
     * 保存
     *
     * @param dto    TabUser 实体对象
     * @param userId {@link Long} 操作用户ID
     * @return TabUserInsertDTO 实体对象
     */
    @Transactional(rollbackFor = Exception.class)
    public @NotNull(message = "返回值不能为null") TabUser insert(
            @Valid @NotNull(message = "【dto】不能为null") final TabUserInsertDTO dto,
            @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        repository.checkUserExist(dto.getUsername(), dto.getPhone(), dto.getEmail());
        final TabUser obj = new TabUser();
        BeanUtils.copyProperties(dto, obj);
        obj.setRoles(Optional.ofNullable(dto.getRoleEncryptIds())
                .filter(org.apache.commons.collections4.CollectionUtils::isNotEmpty)
                .map(roleService::matchValidRoleIds)
                .map(roleIds -> roleIds.toArray(new String[]{}))
                .orElseGet(() -> new String[]{})
        );
        obj.setPassword(passwordEncoder.encode(obj.getPassword()));
        return repository.insert(userId, obj);
    }

    /**
     * 更新数据
     *
     * @param id     {@link Long} 数据ID
     * @param userId {@link Long} 操作用户ID
     * @param dto    TabUserUpdateDTO 实体对象
     */
    @Transactional(rollbackFor = Exception.class)
    public void update(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
            @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId,
            @Valid @NotNull(message = "【dto】不能为null") final TabUserUpdateDTO dto) {
        if (!Strings.isNullOrEmpty(dto.getEmail())) {
            AppCode.A01006.assertHasFalse(repository.exists(
                    QTabUser.tabUser.email.eq(dto.getEmail())
                            .and(QTabUser.tabUser.id.ne(id))
            ));
        }
        if (!Strings.isNullOrEmpty(dto.getPhone())) {
            AppCode.A01007.assertHasFalse(repository.exists(
                    QTabUser.tabUser.phone.eq(dto.getPhone())
                            .and(QTabUser.tabUser.id.ne(id))
            ));
        }

        final TabUser tabUser = findById(id).orElseThrow(NullPointerException::new);
        Code.A00002.assertHasFalse(
                // 管理员角色权限不能编辑, 只能通过编码的方式授权管理员权限： @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
                Objects.equals(true, tabUser.getHidden()),
                "禁止编辑该用户"
        );

        final TabUser obj = new TabUser();
        BeanUtils.copyProperties(dto, obj);
        obj.setRoles(Optional.ofNullable(dto.getRoleEncryptIds())
                .filter(org.apache.commons.collections4.CollectionUtils::isNotEmpty)
                .map(roleService::matchValidRoleIds)
                .map(roleIds -> roleIds.toArray(new String[]{}))
                .orElseGet(() -> new String[]{})
        );
        UpdateRowsException.asserts(repository.update(id, userId, obj));

        clearCache(id);
    }

    /**
     * 用户表 按ID查询对象，注意这里可能有 deleted 为 YES 的数据
     *
     * @param id {@link Long} 数据ID
     * @return {@link Optional<TabUser>} 实体对象
     */
    @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
    public Optional<TabUser> findById(final Long id) {
        if (Objects.isNull(id) || id <= 0) {
            return Optional.empty();
        }
//        return repository.findById(id)
//                .map(TabUser::cloneObject); // 必须要 clone ，如果直接对持久化对象调用 set 方法，会触发更新动作
        return Optional.ofNullable(repository.findCacheById(id)).map(TabUser::cloneObject); // 若使用缓存需要解开代码
    }

    /**
     * 按 id 删除，逻辑删除
     *
     * @param id     {@link Long} 数据ID
     * @param userId {@link Long} 操作用户ID
     */
    @Transactional(rollbackFor = Exception.class)
    public void markDeleteById(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
            @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        final TabUser tabUser = repository.findById(id)
                .orElseThrow(NullPointerException::new);
        Code.A00002.assertHasFalse(
                // 管理员角色权限不能编辑, 只能通过编码的方式授权管理员权限： @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
                Objects.equals(true, tabUser.getHidden()),
                "禁止删除该用户"
        );
        UpdateRowsException.asserts(repository.markDeleteById(id, userId));
        clearCache(id);
    }

    /**
     * 按 id+updateTime 删除，逻辑删除
     *
     * @param id         {@link Long} 数据ID
     * @param updateTime {@link String} 最后一次更新时间
     * @param userId     {@link Long} 操作用户ID
     */
    @Transactional(rollbackFor = Exception.class)
    public void markDeleteById(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id
            , @NotBlank(message = "【updateTime】不能为null") @Size(min = 17, max = 17, message = "【updateTime】必须是 17 位") final String updateTime
            , @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        final TabUser tabUser = repository.findById(id)
                .orElseThrow(NullPointerException::new);
        Code.A00002.assertHasFalse(
                // 管理员角色权限不能编辑, 只能通过编码的方式授权管理员权限： @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
                Objects.equals(true, tabUser.getHidden()),
                "禁止删除该用户"
        );
        UpdateRowsException.asserts(repository.markDeleteById(id, updateTime, userId));
        clearCache(id);
    }

    /**
     * 批量操作按 id 删除，批量逻辑删除
     *
     * @param ids    {@link List<Long>} 数据ID
     * @param userId {@link Long} 操作用户ID
     */
    @Transactional(rollbackFor = Exception.class)
    public void markDeleteByIds(
            @NotEmpty(message = "【ids】不能为空") final Set<@NotNull @Positive Long> ids,
            @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        if (repository.exists(QTabUser.tabUser.id.in(ids)
                .and(QTabUser.tabUser.hidden.eq(true)))
        ) {
            throw new RuntimeException("禁止删除该用户");
        }
        DeleteRowsException.asserts(repository.markDeleteByIds(ids, userId), ids.size());
        ids.forEach(this::clearCache);
    }

    /**
     * 按 id+updateTime 删除，批量逻辑删除
     *
     * @param ids    {@link Set<Long>} 数据ID
     * @param userId {@link Long} 操作用户ID
     */
    @Transactional(rollbackFor = Exception.class)
    public void markDelete(
            @NotEmpty(message = "【ids】不能为空") final Set<@Valid @NotNull Long> ids
            , final Set<String> updateTimes
            , @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId
    ) {
        if (repository.exists(QTabUser.tabUser.id.in(ids)
                .and(QTabUser.tabUser.hidden.eq(true)))
        ) {
            throw new RuntimeException("禁止删除该用户");
        }
        DeleteRowsException.asserts(repository.markDeleteByIds(ids, updateTimes, userId), ids.size());
        ids.forEach(this::clearCache);
    }

    /**
     * 按条件分页查询列表
     *
     * @param condition TabUser 查询条件
     * @param page      {@link Page} 分页排序集合
     * @return {@link QueryResults<TabUser>} 分页对象
     */
    @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
    public @NotNull(message = "返回值不能为null") QueryResults<TabUser> page(
            @NotNull(message = "【condition】不能为null") final TabUser condition,
            @NotNull(message = "【page】不能为null") @Valid final Page page) {
        condition.setHidden(false); // 禁止前端查看隐藏角色
        final QueryResults<TabUser> queryResults = repository.page(condition, page);
        if (queryResults.isEmpty()) {
            return QueryResults.emptyResults();
        }
        final Map<Long, TabUser> userMap = this.mapByIds(queryResults.getResults().stream()
                .flatMap(row -> Stream.of(row.getCreateUserId(), row.getUpdateUserId()))
                .filter(Objects::nonNull)
                .collect(Collectors.toSet())
        );
        queryResults.getResults().forEach(row -> {
            Optional.ofNullable(userMap.get(row.getCreateUserId())).ifPresent(user -> {
                row.setCreateUserNickname(user.getNickname());
            });
            Optional.ofNullable(userMap.get(row.getUpdateUserId())).ifPresent(user -> {
                row.setUpdateUserNickname(user.getNickname());
            });
        });
        return queryResults;
    }

    /**
     * 用户表 按 id 批量查询列表，注意这里可能有 deleted 为 true 的数据
     *
     * @param ids {@link Long}  数据 id 集合
     * @return {@link List<TabUser>} 结果集合
     */
    @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
    public @NotNull(message = "返回值不能为null") List<TabUser> listByIds(final Collection<Long> ids) {
        if (org.springframework.util.CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return Lists.newArrayList(mapByIds(Sets.newHashSet(ids)).values());
    }

    /**
     * 用户表 按 id 批量查询列表，返回 map ， key 为数据 id ， 注意这里可能有 deleted 为 true 的数据
     *
     * @param ids {@link Long} 数据 id 集合
     * @return {@link List<TabUser>} 结果集合
     */
    @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
    public @NotNull(message = "返回值不能为null") Map<Long, TabUser> mapByIds(final Set<Long> ids) {
        if (org.springframework.util.CollectionUtils.isEmpty(ids)) {
            return Collections.emptyMap();
        }
        final RBatch batch = redissonClient.createBatch();
        ids.stream().distinct().forEach(id -> batch.getBucket(cacheKeyById(id), StringCodec.INSTANCE).getAsync());
        // 批量从缓存拿数据
        final Map<Long, TabUser> cacheMap = batch.execute()
                .getResponses()
                .stream()
                // 过滤 null 值， null 表示缓存中不存在，需要从数据库查
                .filter(Objects::nonNull)
                .map(jsonText -> JSON.parseObject((String) jsonText, TabUser.class))
                .collect(Collectors.toMap(TabUser::getId, Function.identity()));
        // 提取缓存中存在的数据ID
        final Set<Long> cacheExistIds = cacheMap.keySet();
        ids.stream().distinct()
                // 排除缓存中存在的数据 id
                .filter(id -> !cacheExistIds.contains(id))
                .forEach(id -> {
                    // 从数据库查询，并放入缓存
                    final TabUser cacheObject = repository.findCacheById(id);
                    if (Objects.nonNull(cacheObject)) {
                        cacheMap.put(id, cacheObject);
                    }
                });
        return cacheMap;
    }

    @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
    public QueryResults<TabUserVO> pageVO(
            @NotNull(message = "【condition】不能为null") final TabUserVO condition,
            @NotNull(message = "【page】不能为null") @Valid final Page page) {
        condition.setHidden(false); // 禁止前端查看隐藏角色
        final QueryResults<TabUserVO> queryResults = repository.page(condition, page, TabUserVO.class);
        if (queryResults.isEmpty()) {
            return QueryResults.emptyResults();
        }
        final Map<Long, TabUser> userMap = this.mapByIds(queryResults.getResults().stream()
                .flatMap(row -> Stream.of(row.getCreateUserId(), row.getUpdateUserId()))
                .filter(Objects::nonNull)
                .collect(Collectors.toSet())
        );
        queryResults.getResults().forEach(row -> {
            Optional.ofNullable(userMap.get(row.getCreateUserId())).ifPresent(user -> {
                row.setCreateUserNickname(user.getNickname());
            });
            Optional.ofNullable(userMap.get(row.getUpdateUserId())).ifPresent(user -> {
                row.setUpdateUserNickname(user.getNickname());
            });
        });
        return queryResults;
    }

    /**
     * 按用户名查找用户信息，（查询范围：username|phone|password）
     *
     * @param username String 用户名
     * @return Optional<TabUser>
     */
    @Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
    public Optional<TabUser> findUser(final String username) {
        return Optional.ofNullable(repository.findUser(username));
    }
//    /**
//     * 启用账户
//     *
//     * @param id     Long 用户ID
//     * @param userId Long 修改者ID
//     */
//    @Transactional(rollbackFor = Exception.class)
//    public void enable(final Long id, final Long userId) {
//        UpdateRowsException.asserts(repository.enable(id, userId));
//    }

    /**
     * 更新数据
     *
     * @param id       {@link Long} 数据ID
     * @param userId   {@link Long} 操作用户ID
     * @param nickname String 昵称
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateNickname(@NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
                               @NotBlank(message = "【nickname】不能为null") final String nickname,
                               @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        UpdateRowsException.asserts(repository.updateNickname(id, nickname, userId));
        clearCache(id);
    }

    /**
     * 修改密码
     *
     * @param id       Long 用户ID
     * @param password String 新密码
     * @param userId   Long 修改者ID
     */
    @Transactional(rollbackFor = Exception.class)
    public void updatePassword(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
            @NotBlank(message = "【password】不能为空") @Size(min = 6, max = 15, message = "【password】必须是 6-15 位") final String password,
            @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        UpdateRowsException.asserts(repository.updatePassword(id, passwordEncoder.encode(password), userId));
        clearCache(id);
    }

    /**
     * 修改禁用启用状态
     *
     * @param id       Long 用户ID
     * @param disabled Boolean 是否禁用
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateDisabled(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
            @NotNull(message = "【disabled】不能为null") final Boolean disabled,
            @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        UpdateRowsException.asserts(repository.updateDisabled(id, disabled, userId));
        clearCache(id);
    }

    /**
     * 重置密码。  这里先返回新密码，基于安全考虑，正确的逻辑应该是发送短信或者邮件给用户，而不是返回新密码
     *
     * @param id     Long 用户ID
     * @param userId Long 修改者ID
     */
    @Transactional(rollbackFor = Exception.class)
    public String resetPassword(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
            @NotNull(message = "【userId】不能为null") @Positive(message = "【userId】必须大于0") final Long userId) {
        final String password = RandomStringUtils.randomAlphanumeric(8);
        updatePassword(id, password, userId);
        return password;
    }

    /**
     * 当前登录用户修改密码
     *
     * @param id          {@link Long} 当前登录用户ID
     * @param passwordOld {@link String} 原密码
     * @param passwordNew {@link String} 新密码
     */
    @Transactional(rollbackFor = Exception.class)
    public void changePassword(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
            @NotBlank(message = "【passwordOld】不能为空") final String passwordOld,
            @NotBlank(message = "【passwordNew】不能为空") final String passwordNew
    ) {
        final TabUser user = getTabUserCacheById(id).orElseThrow(() -> Code.A00002.toCodeException("用户不存在"));
        if (appConfig.getEncrypt().getPwd().isEnabled()) {
            AppCode.A01015.assertHasTrue(
                    passwordEncoder.matches(passwordOld, user.getPassword())
                    , "原密码错误"
            );
            final String passwordNewDecrypt = Rsa.decryptByPrivateKey(
                    passwordNew,
                    appConfig.getEncrypt().getPwd().getPrivateKey()
            );
            updatePassword(id, passwordNewDecrypt, id);
        } else {
            AppCode.A01015.assertHasTrue(
                    passwordEncoder.matches(passwordOld, user.getPassword())
                    , "原密码错误"
            );
            updatePassword(id, passwordNew, id);
        }
    }

    /**
     * 当前登录用户修改头像
     *
     * @param id     {@link Long} 当前登录用户ID
     * @param avatar {@link UserFileInfo} 新头像
     */
    @Transactional(rollbackFor = Exception.class)
    public void changeAvatar(
            @NotNull(message = "【id】不能为null") @Positive(message = "【id】必须大于0") final Long id,
            @NotNull(message = "【avatar】不能为null") final UserFileInfo avatar
    ) {
        UpdateRowsException.asserts(repository.updateAvatar(id, avatar, id));
        clearCache(id);
    }

}
